package pers.ree.demo.math;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.List;

/**
 * BigDecimal的主要数据结构：
 * private final transient long intCompact: 用于存储数值的紧凑表示（不包含小数点），总长度不超过18位时只使用intCompact存储数据
 *
 * private final BigInteger intVal: 用于存储 BigDecimal 的数值部分（即未缩放值，不包含小数点），
 *                                  总长度超过18位时使用intVal存储数据，不超过18位则为null
 *
 * private final int scale: 小数点后位数
 *
 * private transient int precision：数值的总位数（不包含小数点），如123.456，precision=6，
 *              这个值在BigDecimal初始时有可能为0，在需要使用时再临时计算生成
 *
 * 正负符号：signum方法，intCompact的符号位或者intVal.signum
 *
 * 为什么BigDecimal进行修改操作后必须返回一个新的对象，
 * 因为intCompact、intVal和scale都是不可变的
 */
public class BigDecimalDemo {
    public static void main(String[] args) {
        // 验证几种构造方式
        testConstructor();

        // 验证BigDecimal的数据结构
        testStructure();

        // 验证舍入模式
        testRoundingMode();

        // 验证精度
        testScale();

        // 验证除法
        testDiv();
    }

    public static void testConstructor() {
        BigDecimal b1 = new BigDecimal("123.456");
        // 整数部分开头的0会被自动舍弃，但是小数末尾的0不会
        BigDecimal b2 = new BigDecimal("078.0900");
        System.out.println("b2.precision=" + b2.precision());
        int compareResult = b1.compareTo(b2);
        System.out.println("compareResult=" + compareResult);
        BigDecimal b = b1.add(b2);
        System.out.println("b.precision=" + b.precision());

        double d = b.doubleValue();
        System.out.println("d="+d);

        BigDecimal bs = b.setScale(1, RoundingMode.HALF_UP);

        double d1 = bs.doubleValue();
        System.out.println("d1="+d1);
        System.out.println("after HALF_UP, bs.precision="+bs.precision());

        BigDecimal bs2 = b.setScale(5, RoundingMode.HALF_UP);
        double d2 = bs2.doubleValue();
        System.out.println("d2="+d2);
        System.out.println("after HALF_UP, bs2.precision="+bs2.precision());

        String s = bs2.toString();
        System.out.println("s="+s);

        BigDecimal b3 = BigDecimal.valueOf(0.00600);
        System.out.println("b3="+b3.toString());

        // 等同于BigDecimal.valueOf(Double d)
        BigDecimal bd32 = new BigDecimal(Double.toString(0.3434197836437403216));
        System.out.println("bd32="+bd32);
    }

    public static void testStructure() {
        // 字符串含小数点超过MAX_COMPACT_DIGITS（18位）会使用BigInteger intVal 存储数据
        BigDecimal bbi = new BigDecimal("1234567890.123456789");
        System.out.println(bbi);

        // 字符串含小数点未超过18位，intVal=null，只使用intCompact存储数据
        BigDecimal bimpact = new BigDecimal("12345678.123456789");
        System.out.println(bimpact);
    }

    public static void testRoundingMode() {
        /** HALF_UP begin */
        System.out.println("HALF_UP：四舍五入");
        BigDecimal b1 = new BigDecimal("20.545");
        BigDecimal b2 = b1.setScale(2, RoundingMode.HALF_UP);
        // 20.55
        System.out.println(b2.toString());

        b1 = new BigDecimal("20.544");
        b2 = b1.setScale(2, RoundingMode.HALF_UP);
        // 20.54
        System.out.println(b2.toString());

        // 负数
        b1 = new BigDecimal("-20.545");
        b2 = b1.setScale(2, RoundingMode.HALF_UP);
        // -20.55
        System.out.println(b2.toString());


        /** HALF_DOWN begin */
        System.out.println("HALF_DOWN：五舍六入");
        b1 = new BigDecimal("20.546");
        // HALF_DOWN 五舍六入
        b2 = b1.setScale(2, RoundingMode.HALF_DOWN);
        // 20.55
        System.out.println(b2.toString());

        b1 = new BigDecimal("20.545");
        // HALF_DOWN 五舍六入
        b2 = b1.setScale(2, RoundingMode.HALF_DOWN);
        // 20.54
        System.out.println(b2.toString());

        // 负数
        b1 = new BigDecimal("-20.546");
        b2 = b1.setScale(2, RoundingMode.HALF_DOWN);
        // -20.55
        System.out.println(b2.toString());


        /** HALF_EVEN begin */
        // 银行家舍入法。如果舍弃部分左边的数字是奇数，则四舍五入；如果是偶数，则五舍六入。这种方法可以减少累计舍入误差
        System.out.println("HALF_EVEN：银行家舍入");
        b1 = new BigDecimal("12.125");
        b2 = b1.setScale(2, RoundingMode.HALF_EVEN);
        // 舍弃部分左边是12.12，为偶数，所以按五舍六入
        // 12.12
        System.out.println(b2.toString());

        b1 = new BigDecimal("12.115");
        b2 = b1.setScale(2, RoundingMode.HALF_EVEN);
        // 舍弃部分左边是12.11，为奇数，所以按四舍五入
        // 12.12
        System.out.println(b2.toString());

        // 负数
        b1 = new BigDecimal("-20.206");
        b2 = b1.setScale(2, RoundingMode.HALF_EVEN);
        // -20.21
        System.out.println(b2.toString());


        /** UP begin */
        // UP 远离零方向舍入。总是远离零，无论正负
        // 简单说就是进位法，只要舍弃的部分不为0，一律进位
        System.out.println("UP：进位法");
        b1 = new BigDecimal("20.5400001");
        b2 = b1.setScale(2, RoundingMode.UP);
        // 20.55
        System.out.println(b2.toString());

        b1 = new BigDecimal("-20.541");
        b2 = b1.setScale(2, RoundingMode.UP);
        // -20.55
        System.out.println(b2.toString());


        /** DOWN begin */
        System.out.println("DOWN：截位法");
        b1 = new BigDecimal("20.54999");
        b2 = b1.setScale(2, RoundingMode.DOWN);
        // 20.54
        System.out.println(b2.toString());

        b1 = new BigDecimal("-20.541");
        b2 = b1.setScale(2, RoundingMode.DOWN);
        // -20.54
        System.out.println(b2.toString());


        /** CEILING begin */
        // 正数：结果与UP相同，即进位法
        // 负数：结果与DOWN相同，即截位法
        System.out.println("CEILING：趋向正无穷大");
        b1 = new BigDecimal("20.540001");
        b2 = b1.setScale(2, RoundingMode.CEILING);
        // 20.55
        System.out.println(b2.toString());

        b1 = new BigDecimal("-20.549999");
        b2 = b1.setScale(2, RoundingMode.CEILING);
        // -20.54
        System.out.println(b2.toString());


        /** FLOOR begin */
        // 负数：结果与UP相同，即进位法
        // 正数：结果与DOWN相同，即截位法
        System.out.println("FLOOR：趋向负无穷大");
        b1 = new BigDecimal("20.549999");
        b2 = b1.setScale(2, RoundingMode.FLOOR);
        // 20.54
        System.out.println(b2.toString());

        b1 = new BigDecimal("-20.540001");
        b2 = b1.setScale(2, RoundingMode.FLOOR);
        // -20.55
        System.out.println(b2.toString());


        /** UNNECESSARY begin */
        // 舍弃部分不为0时抛出ArithmeticException异常
        System.out.println("UNNECESSARY：非必要舍入");
        b1 = new BigDecimal("20.540");
        b2 = b1.setScale(2, RoundingMode.UNNECESSARY);
        // 20.54
        System.out.println(b2.toString());

        b1 = new BigDecimal("20.540001");
        b2 = b1.setScale(2, RoundingMode.UNNECESSARY);
        // 抛出ArithmeticException异常
        System.out.println(b2.toString());
    }

    public static void testDiv() {
        BigDecimal b1 = new BigDecimal("20");
        BigDecimal b2 = new BigDecimal("3");
//        // BigDecimal在进行除法运算时需设置精度,否则对于除不尽的情况会抛出ArithmeticException异常
//        BigDecimal result = b1.divide(b2);
        // RoundingMode.HALF_UP等同BigDecimal.ROUND_HALF_UP
        BigDecimal result = b1.divide(b2, 2, RoundingMode.HALF_UP);
        System.out.println(result);
    }

    public static void testScale() {
        // 不建议使用double参数的构造方法，结果不可预知
        BigDecimal bd1 = new BigDecimal(0.1);
        // bd1=0.1000000000000000055511151231257827021181583404541015625
        System.out.println("bd1="+bd1);
        // 建议使用
        BigDecimal bd2 = new BigDecimal("0.1");
        // bd2=0.1
        System.out.println("bd2="+bd2);

        // 能正确转换为BigDecimal对象，但是尾部不意义的0会舍弃（BigDecimal(String val)则不会 ），
        // 因为0.10传入valueOf时，作为double类型会自动舍弃结尾无意义的0
        BigDecimal bd3 = BigDecimal.valueOf(0.10);
        // bd3=0.1
        System.out.println("bd3="+bd3);
        BigDecimal bd31 = BigDecimal.valueOf(0.100);
        // 0.1
        System.out.println("bd31="+bd31);
        // 结果与BigDecimal.valueOf一样，舍弃结尾无意义的0
        BigDecimal bd32 = new BigDecimal(Double.toString(0.10));
        // 0.1
        System.out.println("bd32="+bd32);

        // true
        // BigDecimal的equal结果为true的几种情况：
        // 1. 同一个对象
        // 2. 不同对象，但是scale相同，compact相同
        // 此处符合2
        System.out.println("bd3.equals(bd31): " + bd3.equals(bd31));
        // 0
        System.out.println("bd3.compareTo(bd31): " + bd3.compareTo(bd31));

        // BigDecimal(String val)会保留结尾无意义的0
        BigDecimal bd4 = new BigDecimal("0.10");
        // 0.10
        System.out.println("bd4="+bd4);

        BigDecimal bd5 = new BigDecimal("0.100");
        // 0.100
        System.out.println("bd5="+bd5);

        // false，equals需要数值和精度都相同
        System.out.println(bd4.equals(bd5));
        // 0（相等），compareTo只比较数值
        System.out.println(bd4.compareTo(bd5));

        BigDecimal bd11 = new BigDecimal("1.3484");
        BigDecimal bd12 = new BigDecimal("0.947832346");

        BigDecimal bdDiv = bd11.divide(bd12, 2, RoundingMode.HALF_UP);
        double bdBi = bdDiv.toBigInteger().doubleValue();
        System.out.println("bdDiv="+bdDiv);
        System.out.println("bdBi=" + bdBi);

        BigDecimal bd = new BigDecimal("1.6359340234390492");
        // toBigInteger小数部分截位，只保留整数部分
        double d = bd.toBigInteger().doubleValue();
        // 1.0
        System.out.println("toBigInteger().doubleValue="+d);
//        // toBigIntegerExact小数部分不为0，则抛出ArithmeticException异常
//        d = bd.toBigIntegerExact().doubleValue();
//        //
//        System.out.println("toBigIntegerExact().doubleValue="+d);


        // double占8字节，1bit（符号位），11bits（指数位），52bits（尾数位），2^52 = 4503599627370496，一共16位，double的精度为15~16位
        // 实际测试结果，doubleValue时，如果数值超过16位则无法保证精度，如果刚好在16位，有时候能保证，有时候不能，15位则都能保证。这与上述结论基本吻合
        // 上述的16位是指整数部分为0时，小数点后16位，或者整数部分大于0时，整数+小数=16位
        BigDecimal bdDv = new BigDecimal("5.999999999999999");
        // 超过15位小数的部分，不准确
//        参数：1.2359340234390492
//        打印：1.2359340234390493
        double dv = bdDv.doubleValue();
        System.out.println("整数+小数16位: " + dv);

        bdDv = new BigDecimal("5.9999999999999999");
        dv = bdDv.doubleValue();
        // 打印6.0
        System.out.println("整数+小数17位: " + dv);
    }
}
